/*-------------------------------------------------------------------------
 *
 * pg_regress --- regression test driver
 *
 * This is a C implementation of the previous shell script for running
 * the regression tests, and should be mostly compatible with it.
 * Initial author of C translation: Magnus Hagander
 *
 * This code is released under the terms of the PostgreSQL License.
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * This source code file contains modifications made by THL A29 Limited ("Tencent Modifications").
 * All Tencent Modifications are Copyright (C) 2023 THL A29 Limited.
 *
 * src/test/regress/pg_regress.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <ctype.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <pwd.h>

#ifdef HAVE_SYS_RESOURCE_H
#include <sys/time.h>
#include <sys/resource.h>
#endif

#include "pg_regress.h"

#include "common/restricted_token.h"
#include "common/username.h"
#include "getopt_long.h"
#include "libpq/pqcomm.h"        /* needed for UNIXSOCK_PATH() */
#include "pg_config_paths.h"

#ifdef PGXC
/*
 * In Postgres-XC, a regression test check is run on 2 Coordinators
 * and 2 Datanodes. Coordinator 1 is considered as being the default
 * used in pg_regress.
 * External connections are made to Coordinator 1.
 * All connections to remote nodes are made from Coordinator 1.
 * Here is the list of nodes identified with a unique ID.
 */
typedef enum
{
    PGXC_COORD_1,
    PGXC_COORD_2,
    PGXC_DATANODE_1,
    PGXC_DATANODE_2,
    PGXC_GTM
} PGXCNodeTypeNum;
#endif

/* for resultmap we need a list of pairs of strings */
typedef struct _resultmap
{
    char       *test;
    char       *type;
    char       *resultfile;
    struct _resultmap *next;
} _resultmap;

/*
 * Values obtained from Makefile.
 */
char       *host_platform = HOST_TUPLE;

#ifndef WIN32                    /* not used in WIN32 case */
static char *shellprog = SHELLPROG;
#endif

/*
 * On Windows we use -w in diff switches to avoid problems with inconsistent
 * newline representation.  The actual result files will generally have
 * Windows-style newlines, but the comparison files might or might not.
 */
#ifndef WIN32
const char *basic_diff_opts = "";
const char *pretty_diff_opts = "-C3";
#else
const char *basic_diff_opts = "-w";
const char *pretty_diff_opts = "-w -C3";
#endif

/* options settable from command line */
_stringlist *dblist = NULL;
bool        debug = false;
char       *inputdir = ".";
char       *outputdir = ".";
char       *bindir = PGBINDIR;
char       *launcher = NULL;
static _stringlist *loadlanguage = NULL;
static _stringlist *loadextension = NULL;
static int    max_connections = 0;
static char *encoding = NULL;
static _stringlist *schedulelist = NULL;
static _stringlist *extra_tests = NULL;
static char *temp_instance = NULL;
static _stringlist *temp_configs = NULL;
static bool nolocale = false;
static bool use_existing = false;
static char *hostname = NULL;
static int    port = -1;
#ifdef PGXC
/*
 * Additional port numbers for Coordinator 2 and Datanode 1&2
 * Ports are chosen up to the default value which is 5432.
 */
static int    port_coord2 = -1;
static int    port_dn1 = -1;
static int    port_dn2 = -1;
static int    port_gtm = -1;
/*
 * Poolers of Coordinators 1 and 2 need an additional port value
 * taken as the default value and the next value.
 */
static int    co1_pooler_port = -1;
static int    co2_pooler_port = -1;
static int    dn1_pooler_port = -1;
static int    dn2_pooler_port = -1;

/* Data folder of each node */
const char *data_co1 = "data_co1"; /* Coordinator 1 */
const char *data_co2 = "data_co2"; /* Coordinator 2 */
const char *data_dn1 = "data_dn1"; /* Datanode 1 */
const char *data_dn2 = "data_dn2"; /* Datanode 2 */
const char *data_gtm = "data_gtm"; /* GTM */
/* Node names */
const char *name_co1 = "coord1"; /* Coordinator 1 */
const char *name_co2 = "coord2"; /* Coordinator 2 */
const char *name_dn1 = "datanode_1"; /* Datanode 1 */
const char *name_dn2 = "datanode_2"; /* Datanode 2 */

/* 7 port numbers are needed */
#define PORT_NUM_INTERVAL 7
#endif
static bool port_specified_by_user = false;
static char *dlpath = PKGLIBDIR;
static char *user = NULL;
static _stringlist *extraroles = NULL;
static char *config_auth_datadir = NULL;

/* internal variables */
static const char *progname;
static char *logfilename;
static FILE *logfile;
static char *difffilename;
static const char *sockdir;
#ifdef HAVE_UNIX_SOCKETS
static const char *temp_sockdir;
static char sockself[MAXPGPATH];
static char socklock[MAXPGPATH];
#endif

static _resultmap *resultmap = NULL;

#ifdef PGXC
/* Additional PIDs for PGXC temporary nodes */
static PID_TYPE coord1_pid = INVALID_PID;
static PID_TYPE coord2_pid = INVALID_PID;
static PID_TYPE dn1_pid = INVALID_PID;
static PID_TYPE dn2_pid = INVALID_PID;
static PID_TYPE gtm_pid = INVALID_PID;
#else
static PID_TYPE postmaster_pid = INVALID_PID;
#endif
static bool postmaster_running = false;

static int    success_count = 0;
static int    fail_count = 0;
static int    fail_ignore_count = 0;

static bool directory_exists(const char *dir);
static void make_directory(const char *dir);

#ifdef PGXC
static void
psql_command_node(const char *database, PGXCNodeTypeNum node, const char *query,...)
/* This extension allows gcc to check the format string for consistency with
   the supplied arguments. */
__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
#endif

static void header(const char *fmt,...) pg_attribute_printf(1, 2);
static void status(const char *fmt,...) pg_attribute_printf(1, 2);
static void psql_command(const char *database, const char *query,...) pg_attribute_printf(2, 3);

/*
 * allow core files if possible.
 */
#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
static void
unlimit_core_size(void)
{
    struct rlimit lim;

    getrlimit(RLIMIT_CORE, &lim);
    if (lim.rlim_max == 0)
    {
        fprintf(stderr,
                _("%s: could not set core size: disallowed by hard limit\n"),
                progname);
        return;
    }
    else if (lim.rlim_max == RLIM_INFINITY || lim.rlim_cur < lim.rlim_max)
    {
        lim.rlim_cur = lim.rlim_max;
        setrlimit(RLIMIT_CORE, &lim);
    }
}
#endif


/*
 * Add an item at the end of a stringlist.
 */
void
add_stringlist_item(_stringlist **listhead, const char *str)
{
    _stringlist *newentry = pg_malloc(sizeof(_stringlist));
    _stringlist *oldentry;

    newentry->str = pg_strdup(str);
    newentry->next = NULL;
    if (*listhead == NULL)
        *listhead = newentry;
    else
    {
        for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next)
             /* skip */ ;
        oldentry->next = newentry;
    }
}

/*
 * Free a stringlist.
 */
static void
free_stringlist(_stringlist **listhead)
{
    if (listhead == NULL || *listhead == NULL)
        return;
    if ((*listhead)->next != NULL)
        free_stringlist(&((*listhead)->next));
    free((*listhead)->str);
    free(*listhead);
    *listhead = NULL;
}

/*
 * Split a delimited string into a stringlist
 */
static void
split_to_stringlist(const char *s, const char *delim, _stringlist **listhead)
{
    char       *sc = pg_strdup(s);
    char       *token = strtok(sc, delim);

    while (token)
    {
        add_stringlist_item(listhead, token);
        token = strtok(NULL, delim);
    }
    free(sc);
}

/*
 * Print a progress banner on stdout.
 */
static void
header(const char *fmt,...)
{
    char        tmp[64];
    va_list        ap;

    va_start(ap, fmt);
    vsnprintf(tmp, sizeof(tmp), fmt, ap);
    va_end(ap);

    fprintf(stdout, "============== %-38s ==============\n", tmp);
    fflush(stdout);
}

/*
 * Print "doing something ..." --- supplied text should not end with newline
 */
static void
status(const char *fmt,...)
{
    va_list        ap;

    va_start(ap, fmt);
    vfprintf(stdout, fmt, ap);
    fflush(stdout);
    va_end(ap);

    if (logfile)
    {
        va_start(ap, fmt);
        vfprintf(logfile, fmt, ap);
        va_end(ap);
    }
}

/*
 * Done "doing something ..."
 */
static void
status_end(void)
{
    fprintf(stdout, "\n");
    fflush(stdout);
    if (logfile)
        fprintf(logfile, "\n");
}

#ifdef PGXC
/*
 * Find data folder associated to node
 */
static
const char *
find_data_folder(PGXCNodeTypeNum node)
{
    const char *data_folder;

    switch (node)
    {
        case PGXC_COORD_1:
            data_folder = data_co1;
            break;
        case PGXC_COORD_2:
            data_folder = data_co2;
            break;
        case PGXC_DATANODE_1:
            data_folder = data_dn1;
            break;
        case PGXC_DATANODE_2:
            data_folder = data_dn2;
            break;
        case PGXC_GTM:
            data_folder = data_gtm;
            break;
        default:
            /* Should not happen */
            data_folder = NULL;
            break;
    }

    return data_folder;
}

/*
 * Stop GTM process
 */
static void
stop_gtm(void)
{
    const char  *data_folder = find_data_folder(PGXC_GTM);
    int            r;
    char        buf[MAXPGPATH * 2];

    /* On Windows, system() seems not to force fflush, so... */
    fflush(stdout);
    fflush(stderr);

    snprintf(buf, sizeof(buf),
             "\"%s%sgtm_ctl\" stop -Z gtm -D \"%s/%s\" -m fast",
             bindir ? bindir : "",
             bindir ? "/" : "",
             temp_instance, data_folder);
    r = system(buf);
    if (r != 0)
    {
        fprintf(stderr, _("\n%s: could not stop GTM: exit code was %d\n"),
                progname, r);
        exit(2);
    }
}

/*
 * Stop the given node
 */
static void
stop_node(PGXCNodeTypeNum node)
{
    const char *data_folder = find_data_folder(node);
    char        buf[MAXPGPATH * 2];
    int            r;

    /* On Windows, system() seems not to force fflush, so... */
    fflush(stdout);
    fflush(stderr);

    snprintf(buf, sizeof(buf),
             "\"%s%spg_ctl\" stop -D \"%s/%s\" -s -m fast",
             bindir ? bindir : "",
             bindir ? "/" : "",
             temp_instance, data_folder);
    r = system(buf);
    if (r != 0)
    {
        fprintf(stderr, _("\n%s: could not stop postmaster: exit code was %d\n"),
                progname, r);
        exit(2);
    }
}
#endif

/*
 * shut down temp postmaster
 */
static void
stop_postmaster(void)
{
    if (postmaster_running)
    {
#ifdef PGXC
        /*
         * It is necessary to stop first the Coordinator 1,
         * Then other nodes are stopped nicely.
         * This is due to connection dependencies between nodes.
         */
        stop_node(PGXC_COORD_1);
        stop_node(PGXC_COORD_2);
        stop_node(PGXC_DATANODE_1);
        stop_node(PGXC_DATANODE_2);

        /* Stop GTM at the end */
        stop_gtm();
#else
        /* We use pg_ctl to issue the kill and wait for stop */
        char        buf[MAXPGPATH * 2];
        int            r;

        /* On Windows, system() seems not to force fflush, so... */
        fflush(stdout);
        fflush(stderr);

        snprintf(buf, sizeof(buf),
                 "\"%s%spg_ctl\" stop -D \"%s/data\" -s",
                 bindir ? bindir : "",
                 bindir ? "/" : "",
                 temp_instance);
        r = system(buf);
        if (r != 0)
        {
            fprintf(stderr, _("\n%s: could not stop postmaster: exit code was %d\n"),
                    progname, r);
            _exit(2);            /* not exit(), that could be recursive */
        }
#endif

        postmaster_running = false;
    }
}

#ifdef PGXC
/*
 * Handy subroutine for setting an environment variable "var" to "val"
 */
static void
doputenv(const char *var, const char *val)
{
    char       *s = malloc(strlen(var) + strlen(val) + 2);

    sprintf(s, "%s=%s", var, val);
    putenv(s);
}

/*
 * Get node port of associated node
 */
static int
get_port_number(PGXCNodeTypeNum node)
{
    switch (node)
    {
        case PGXC_COORD_1:
            return port;
        case PGXC_COORD_2:
            return port_coord2;
        case PGXC_DATANODE_1:
            return port_dn1;
        case PGXC_DATANODE_2:
            return port_dn2;
        case PGXC_GTM:
            return port_gtm;
        default:
            /* Should not happen */
            return -1;
    }
}

/*
 * Get node name of associated node
 */
static const char *
get_node_name(PGXCNodeTypeNum node)
{
    switch (node)
    {
        case PGXC_COORD_1:
            return name_co1;
        case PGXC_COORD_2:
            return name_co2;
        case PGXC_DATANODE_1:
            return name_dn1;
        case PGXC_DATANODE_2:
            return name_dn2;
        case PGXC_GTM:
        default:
            /* Should not happen */
            return NULL;
    }
}

/*
 * Get pooler port number
 */
static int
get_pooler_port(PGXCNodeTypeNum node)
{
    switch (node)
    {
        case PGXC_COORD_1:
            return co1_pooler_port;
        case PGXC_COORD_2:
            return co2_pooler_port;
        case PGXC_DATANODE_1:
            return dn1_pooler_port;
        case PGXC_DATANODE_2:
            return dn2_pooler_port;
        default:
            /* Should not happen */
            return -1;
    }
}

/*
 * Set port number for given node
 */
static void
set_port_number(PGXCNodeTypeNum node, int port_number)
{
    switch (node)
    {
        case PGXC_COORD_1:
            port = port_number;
            break;
        case PGXC_COORD_2:
            port_coord2 = port_number;
            break;
        case PGXC_DATANODE_1:
            port_dn1 = port_number;
            break;
        case PGXC_DATANODE_2:
            port_dn2 = port_number;
            break;
        case PGXC_GTM:
            port_gtm = port_number;
            break;
        default:
            /* Should not happen */
            break;
    }
}

/*
 * Calculate port number for given node.
 * If node is main access point, set also environment variable PGPORT.
 */
static void
calculate_node_port(PGXCNodeTypeNum node, bool is_main)
{// #lizard forgives
    int            port_number, i;
    char        buf[MAXPGPATH * 4];

    if (is_main)
    {
        char       *pgport;

        /*
         * Try to respect environment setting
         * or fallback to default
         */
        pgport = getenv("PGPORT");
        if (!pgport)
            port_number = get_port_number(node);
        else
            port_number = atoi(pgport);
    }
    else
    {
        /* Get port number for node */
        port_number = get_port_number(node);
    }

    /*
     * Check if there is a postmaster running already.
     */
    snprintf(buf, sizeof(buf),
             "\"%s%spsql\" -p %d -X postgres <%s 2>%s",
             bindir ? bindir : "",
             bindir ? "/" : "",
             port_number, DEVNULL, DEVNULL);

    for (i = 0; i < 16; i++)
    {
        if (system(buf) == 0)
        {

            if ((is_main && port_specified_by_user) ||
                i == 15)
            {
                fprintf(stderr, _("port %d apparently in use\n"), port);
                if (!port_specified_by_user)
                    fprintf(stderr, _("%s: could not determine an available port\n"), progname);
                fprintf(stderr, _("Specify an unused port using the --port option or shut down any conflicting PostgreSQL servers.\n"));
                exit(2);
            }

            fprintf(stderr, _("port %d apparently in use, trying %d\n"),
                    port_number, port_number + 1);

            /*
             * If port is already in use, jump to a value that is not covered by
             * other nodes (Coordinator, Datanode, GTM and poolers)
             */
            port_number += PORT_NUM_INTERVAL;

            if (is_main)
            {
                char s[16];

                sprintf(s, "%d", port_number);
                doputenv("PGPORT", s);
            }
        }
        else
            break;
    }

    /* Save static value of port for node */
    set_port_number(node, port_number);
}

/*
 * Set PID number for given node
 */
static void
set_node_pid(PGXCNodeTypeNum node, PID_TYPE pid_number)
{
    switch (node)
    {
        case PGXC_COORD_1:
            coord1_pid = pid_number;
            break;
        case PGXC_COORD_2:
            coord2_pid = pid_number;
            break;
        case PGXC_DATANODE_1:
            dn1_pid = pid_number;
            break;
        case PGXC_DATANODE_2:
            dn2_pid = pid_number;
            break;
        case PGXC_GTM:
            gtm_pid = pid_number;
            break;
        default:
            /* Should not happen */
            break;
    }
}

/*
 * Get PID number for given node
 */
static PID_TYPE
get_node_pid(PGXCNodeTypeNum node)
{
    switch (node)
    {
        case PGXC_COORD_1:
            return coord1_pid;
        case PGXC_COORD_2:
            return coord2_pid;
        case PGXC_DATANODE_1:
            return dn1_pid;
        case PGXC_DATANODE_2:
            return dn2_pid;
        case PGXC_GTM:
            return gtm_pid;
        default:
            /* Should not happen */
            return -1;
    }
}


/*
 * Start given node
 */
static void
start_node(PGXCNodeTypeNum node, bool is_coord, bool is_main)
{// #lizard forgives
    const char *data_folder = find_data_folder(node);
    int            port_number = get_port_number(node);
    PID_TYPE    node_pid;
    char        buf[MAXPGPATH * 4];

    if (node == PGXC_GTM)
    {
        /* Case of a GTM start */
        header(_("starting GTM process"));
        snprintf(buf, sizeof(buf),
                 "\"%s%sgtm\" -D \"%s/%s\" -p %d -x 10000 > \"%s/log/gtm.log\" 2>&1",
                 bindir ? bindir : "",
                 bindir ? "/" : "",
                 temp_instance, data_folder, port_number,
                 outputdir);
    }
    else
    {
        /* Case of normal nodes, start the node */
        if (is_main)
            snprintf(buf, sizeof(buf),
                     "\"%s%spostgres\" %s -i -p %d -D \"%s/%s\"%s "
                     "-c \"listen_addresses=%s\" -k \"%s\" "
                     "> \"%s/log/postmaster_%d.log\" 2>&1",
                     bindir ? bindir : "",
                     bindir ? "/" : "",
                     is_coord ? "--coordinator" : "--datanode",
                     port_number,
                     temp_instance, data_folder,
                     debug ? " -d 5" : "",
                     hostname ? hostname : "", sockdir ? sockdir : "",
                     outputdir, node);
        else
            snprintf(buf, sizeof(buf),
                     "\"%s%spostgres\" %s -i -p %d -D \"%s/%s\"%s "
                     "-k \"%s\" "
                     "> \"%s/log/postmaster_%d.log\" 2>&1",
                     bindir ? bindir : "",
                     bindir ? "/" : "",
                     is_coord ? "--coordinator" : "--datanode",
                     port_number,
                     temp_instance, data_folder,
                     debug ? " -d 5" : "",
                     sockdir ? sockdir : "",
                     outputdir, node);
    }

    /* Check process spawn */
    node_pid = spawn_process(buf);
    if (node_pid == INVALID_PID)
    {
        if (node == PGXC_GTM)
            fprintf(stderr, _("\n%s: could not spawn GTM: %s\n"),
                progname, strerror(errno));
        else
            fprintf(stderr, _("\n%s: could not spawn postmaster: %s\n"),
                    progname, strerror(errno));
        exit(2);
    }

    /* Wait a little for full start */
    pg_usleep(1000000L);

    /* Save static PID number */
    set_node_pid(node,node_pid);
}

/*
 * Initialize given node with initdb
 */
static void
initdb_node(PGXCNodeTypeNum node)
{// #lizard forgives
    const char *data_folder = find_data_folder(node);
    char        buf[MAXPGPATH * 4];

    if (node == PGXC_GTM)
    {
        snprintf(buf, sizeof(buf),
                 "\"%s%sinitgtm\" -Z gtm -D \"%s/%s\" --noclean%s > \"%s/log/initgtm.log\" 2>&1",
                 bindir ? bindir : "",
                 bindir ? "/" : "",
                 temp_instance, data_folder,
                 debug ? " --debug" : "",
                 outputdir);
        if (system(buf))
        {
            fprintf(stderr, _("\n%s: initgtm failed\nExamine %s/log/initgtm.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
            exit(2);
        }
    }
    else
    {

        
        snprintf(buf, sizeof(buf),
                 "\"%s%sinitdb\" --nodename %s --nodetype %s -D \"%s/%s\"  --noclean%s%s "
                 "--master_gtm_nodename %s --master_gtm_ip %s --master_gtm_port %d > \"%s/log/initdb.log\" 2>&1",
                 bindir ? bindir : "",
                 bindir ? "/" : "",
                 (char *)get_node_name(node), 
                 (PGXC_COORD_1 == node || PGXC_COORD_2 == node) ? "coordinator": "datanode",
                 temp_instance, data_folder, 
                 debug ? " --debug" : "",
                 nolocale ? " --no-locale" : "",
                 "a_one",
                 "localhost",
                 get_port_number(PGXC_GTM),
                 outputdir);
        if (system(buf))
        {
            fprintf(stderr, _("\n%s: initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
            exit(2);
        }
    }
}


static void
set_node_config_file(PGXCNodeTypeNum node)
{
    const char *data_folder = find_data_folder(node);
    FILE       *pg_conf;
    char        buf[MAXPGPATH * 4];
    _stringlist *sl;

    snprintf(buf, sizeof(buf), "%s/%s/postgresql.conf", temp_instance, data_folder);
    pg_conf = fopen(buf, "a");
    if (pg_conf == NULL)
    {
        fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n"), progname, buf, strerror(errno));
        exit(2);
    }
    fputs("\n# Configuration added by pg_regress\n\n", pg_conf);

    /*
     * Cluster uses 2PC for write transactions involving multiple nodes
     * This has to be set at least to a value corresponding to the maximum
     * number of tests run in parallel.
     */
    fputs("max_prepared_transactions = 2048\n", pg_conf);

    /*
     * Set sequence_range to 1 for deterministic results
     */
    fputs("sequence_range = 1\n", pg_conf);
    fputs("log_line_prefix = \'\%m [\%p]:xid[\%x-\%v] remote=\%R,coord=\%C,global_session=\%S \'\n", pg_conf);

    fputs("log_error_verbosity = 'verbose'\n", pg_conf);

    /* log on */
    fputs("log_destination = 'csvlog'\n", pg_conf);
    fputs("logging_collector = on\n", pg_conf);
    fputs("log_directory = 'pg_log'\n", pg_conf);

    snprintf(buf, sizeof(buf), "pooler_port = %d\n", get_pooler_port(node));
    fputs(buf, pg_conf);

    fputs("log_min_messages = log\n", pg_conf);
    fputs("log_min_error_statement = log\n", pg_conf);
	fputs("max_connections = 500\n", pg_conf);
	fputs("max_pool_size = 500\n", pg_conf);
    fputs("max_worker_processes = 256\n", pg_conf);
    fputs("max_parallel_workers  = 256\n", pg_conf);
    fputs("enable_statistic = on\n", pg_conf);

    /* Set pooler port for Coordinators */    
    snprintf(buf, sizeof(buf), "pooler_port = %d\n", get_pooler_port(node));
    fputs(buf, pg_conf);

    fputs("enable_pooler_debug_print = on\n", pg_conf);

    for (sl = temp_configs; sl != NULL; sl = sl->next)
    {
        char       *temp_config = sl->str;
        FILE       *extra_conf;
        char        line_buf[1024];

        extra_conf = fopen(temp_config, "r");
        if (extra_conf == NULL)
        {
            fprintf(stderr, _("\n%s: could not open \"%s\" to read extra config: %s\n"), progname, temp_config, strerror(errno));
            exit(2);
        }
        while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL)
            fputs(line_buf, pg_conf);
        fclose(extra_conf);
    }

    fclose(pg_conf);
}

/*
 * Issue a command via psql, connecting to the specified database on wanted node
 * Since we use system(), this doesn't return until the operation finishes
 * This code is duplicated with psql_command but this way code impact is limited.
 */
static void
psql_command_node(const char *database, PGXCNodeTypeNum node, const char *query,...)
{
    char        query_formatted[1024];
    char        query_escaped[2048];
    char        psql_cmd[MAXPGPATH + 2048];
    va_list        args;
    char       *s;
    char       *d;

    /* Generate the query with insertion of sprintf arguments */
    va_start(args, query);
    vsnprintf(query_formatted, sizeof(query_formatted), query, args);
    va_end(args);

    /* Now escape any shell double-quote metacharacters */
    d = query_escaped;
    for (s = query_formatted; *s; s++)
    {
        if (strchr("\\\"$`", *s))
            *d++ = '\\';
        *d++ = *s;
    }
    *d = '\0';

    /* And now we can build and execute the shell command */
    snprintf(psql_cmd, sizeof(psql_cmd),
             "\"%s%spsql\" -X -p %d -c \"%s\" \"%s\"",
             bindir ? bindir : "",
             bindir ? "/" : "",
             get_port_number(node),
             query_escaped,
             database);

    if (system(psql_cmd) != 0)
    {
        /* psql probably already reported the error */
        fprintf(stderr, _("command failed: %s\n"), psql_cmd);
        exit(2);
    }
}

/*
 * Setup connection information to remote nodes for Coordinator running regression
 */
static void
setup_connection_information(void)
{
    header(_("setting connection information"));

    /* -----coordinator 1 ------- */
    /* node info in Coordinator 1 */
    psql_command_node("postgres", PGXC_COORD_1, "ALTER NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d);",
                      (char *)get_node_name(PGXC_COORD_1),
                      get_port_number(PGXC_COORD_1));
    /* Remote Coordinator on Coordinator 1 */
    psql_command_node("postgres", PGXC_COORD_1, "CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d);",
                      (char *)get_node_name(PGXC_COORD_2),
                      get_port_number(PGXC_COORD_2));
    /* Datanodes on Coordinator 1 */
    psql_command_node("postgres", PGXC_COORD_1, "CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d, PRIMARY, PREFERRED);",
                      (char *)get_node_name(PGXC_DATANODE_1),
                      get_port_number(PGXC_DATANODE_1));
    psql_command_node("postgres", PGXC_COORD_1, "CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d);",
                      (char *)get_node_name(PGXC_DATANODE_2),
                      get_port_number(PGXC_DATANODE_2));

    /* Then reload the connection data */
    psql_command_node("postgres", PGXC_COORD_1, "SELECT pgxc_pool_reload();");

    /* -----coordinator 2 ------- */

    /* Remote Coordinator on Coordinator 2 */
    psql_command_node("postgres", PGXC_COORD_2, "CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d);",
                      (char *)get_node_name(PGXC_COORD_1),
                      get_port_number(PGXC_COORD_1));
    /* node info in Coordinator 2 */
    psql_command_node("postgres", PGXC_COORD_2, "ALTER NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d);",
                      (char *)get_node_name(PGXC_COORD_2),
                      get_port_number(PGXC_COORD_2));
    /* Datanodes on Coordinator 2 */
    psql_command_node("postgres", PGXC_COORD_2, "CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d, PRIMARY, PREFERRED);",
                      (char *)get_node_name(PGXC_DATANODE_1),
                      get_port_number(PGXC_DATANODE_1));
    psql_command_node("postgres", PGXC_COORD_2, "CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d);",
                      (char *)get_node_name(PGXC_DATANODE_2),
                      get_port_number(PGXC_DATANODE_2));
    /* Then reload the connection data */
    psql_command_node("postgres", PGXC_COORD_2, "SELECT pgxc_pool_reload();");

    /* -----datanode 1 ------- */

    /* Remote Coordinator 1 on Datanode 1 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d)$$;",
                      (char *)get_node_name(PGXC_DATANODE_1),
                      (char *)get_node_name(PGXC_COORD_1),
                      get_port_number(PGXC_COORD_1));
    /* Remote Coordinator 2 on Datanode 1 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d)$$;",
                      (char *)get_node_name(PGXC_DATANODE_1),
                      (char *)get_node_name(PGXC_COORD_2),
                      get_port_number(PGXC_COORD_2));
    /* node info in Datanode 1 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$ ALTER NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d, PRIMARY, PREFERRED)$$;",
                      (char *)get_node_name(PGXC_DATANODE_1),
                      (char *)get_node_name(PGXC_DATANODE_1),
                      get_port_number(PGXC_DATANODE_1));
    /* Remote Datanode 2 on Datanode 1 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d)$$;",
                      (char *)get_node_name(PGXC_DATANODE_1),
                      (char *)get_node_name(PGXC_DATANODE_2),
                      get_port_number(PGXC_DATANODE_2));
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$SELECT pgxc_pool_reload()$$;",
                      (char *)get_node_name(PGXC_DATANODE_1));
        
    /* -----datanode 2 ------- */

    /* Remote Coordinator 1 on Datanode 2 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d)$$;",
                      (char *)get_node_name(PGXC_DATANODE_2),
                      (char *)get_node_name(PGXC_COORD_1),
                      get_port_number(PGXC_COORD_1));
    /* Remote Coordinator 2 on Datanode 2 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'coordinator', PORT = %d)$$;",
                      (char *)get_node_name(PGXC_DATANODE_2),
                      (char *)get_node_name(PGXC_COORD_2),
                      get_port_number(PGXC_COORD_2));
    /* Remote Datanode 1 on Datanode 2 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$CREATE NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d, PRIMARY, PREFERRED)$$;",
                      (char *)get_node_name(PGXC_DATANODE_2),
                      (char *)get_node_name(PGXC_DATANODE_1),
                      get_port_number(PGXC_DATANODE_1));
    /* node info in Datanode 2 */
    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$ ALTER NODE %s WITH (HOST = 'localhost',"
                      " type = 'datanode', PORT = %d)$$;",
                      (char *)get_node_name(PGXC_DATANODE_2),
                      (char *)get_node_name(PGXC_DATANODE_2),
                      get_port_number(PGXC_DATANODE_2));

    psql_command_node("postgres", PGXC_COORD_1, "EXECUTE DIRECT ON ( %s ) $$SELECT pgxc_pool_reload()$$;",
                      (char *)get_node_name(PGXC_DATANODE_2));

#ifdef __OPENTENBASE__
    sleep(10);
    psql_command_node("postgres", PGXC_COORD_1, "create DEFAULT node group default_group with (%s, %s);",
                      (char *)get_node_name(PGXC_DATANODE_1),
                      (char *)get_node_name(PGXC_DATANODE_2));
    psql_command_node("postgres", PGXC_COORD_1, "create sharding group to group default_group;");
    psql_command_node("postgres", PGXC_COORD_1, "clean sharding;");
#endif
}


/*
 * Check if node is already running
 */
static bool
check_node_running(PGXCNodeTypeNum node)
{
    char        buf[MAXPGPATH * 4];
    struct      passwd *pwd;
    
    pwd = getpwuid(getuid());
    snprintf(buf, sizeof(buf),
             "\"%s%spsql\" -p %d -X postgres <%s 2>%s.%s",
             bindir ? bindir : "",
             bindir ? "/" : "",
             get_port_number(node), DEVNULL, "/tmp/makecheck.log", pwd->pw_name);
    return system(buf) == 0;
}


/*
 * Check if given node has failed during startup
 */
static void
check_node_fail(PGXCNodeTypeNum node)
{
    PID_TYPE    pid_number = get_node_pid(node);

#ifndef WIN32
    if (kill(pid_number, 0) != 0)
#else
    if (WaitForSingleObject(pid_number, 0) == WAIT_OBJECT_0)
#endif /* WIN32 */
    {
        fprintf(stderr, _("\n%s: postmaster failed\nExamine %s/log/postmaster_%d.log for the reason\n"), progname, outputdir, node);
        exit(2);
    }
}

/*
 * Kill given node but do not exit
 */
static void
kill_node(PGXCNodeTypeNum node)
{
    PID_TYPE pid_number = get_node_pid(node);

    fprintf(stderr, _("\n%s: postmaster did not respond within 60 seconds\nExamine %s/log/postmaster_%d.log for the reason\n"), progname, outputdir, node);

#ifndef WIN32
    if (kill(pid_number, SIGKILL) != 0 &&
        errno != ESRCH)
        fprintf(stderr, _("\n%s: could not kill failed postmaster: %s\n"),
                progname, strerror(errno));
#else
    if (TerminateProcess(pid_number, 255) == 0)
        fprintf(stderr, _("\n%s: could not kill failed postmaster: %lu\n"),
                progname, GetLastError());
#endif
}
#endif

#ifdef HAVE_UNIX_SOCKETS
/*
 * Remove the socket temporary directory.  pg_regress never waits for a
 * postmaster exit, so it is indeterminate whether the postmaster has yet to
 * unlink the socket and lock file.  Unlink them here so we can proceed to
 * remove the directory.  Ignore errors; leaking a temporary directory is
 * unimportant.  This can run from a signal handler.  The code is not
 * acceptable in a Windows signal handler (see initdb.c:trapsig()), but
 * Windows is not a HAVE_UNIX_SOCKETS platform.
 */
static void
remove_temp(void)
{
    Assert(temp_sockdir);
    unlink(sockself);
    unlink(socklock);
    rmdir(temp_sockdir);
}

/*
 * Signal handler that calls remove_temp() and reraises the signal.
 */
static void
signal_remove_temp(int signum)
{
    remove_temp();

    pqsignal(signum, SIG_DFL);
    raise(signum);
}

/*
 * Create a temporary directory suitable for the server's Unix-domain socket.
 * The directory will have mode 0700 or stricter, so no other OS user can open
 * our socket to exploit our use of trust authentication.  Most systems
 * constrain the length of socket paths well below _POSIX_PATH_MAX, so we
 * place the directory under /tmp rather than relative to the possibly-deep
 * current working directory.
 *
 * Compared to using the compiled-in DEFAULT_PGSOCKET_DIR, this also permits
 * testing to work in builds that relocate it to a directory not writable to
 * the build/test user.
 */
static const char *
make_temp_sockdir(void)
{
    char       *template = pg_strdup("/tmp/pg_regress-XXXXXX");

    temp_sockdir = mkdtemp(template);
    if (temp_sockdir == NULL)
    {
        fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
                progname, template, strerror(errno));
        exit(2);
    }

    /* Stage file names for remove_temp().  Unsafe in a signal handler. */
    UNIXSOCK_PATH(sockself, port, temp_sockdir);
    snprintf(socklock, sizeof(socklock), "%s.lock", sockself);

    /* Remove the directory during clean exit. */
    atexit(remove_temp);

    /*
     * Remove the directory before dying to the usual signals.  Omit SIGQUIT,
     * preserving it as a quick, untidy exit.
     */
    pqsignal(SIGHUP, signal_remove_temp);
    pqsignal(SIGINT, signal_remove_temp);
    pqsignal(SIGPIPE, signal_remove_temp);
    pqsignal(SIGTERM, signal_remove_temp);

    return temp_sockdir;
}
#endif                            /* HAVE_UNIX_SOCKETS */

/*
 * Check whether string matches pattern
 *
 * In the original shell script, this function was implemented using expr(1),
 * which provides basic regular expressions restricted to match starting at
 * the string start (in conventional regex terms, there's an implicit "^"
 * at the start of the pattern --- but no implicit "$" at the end).
 *
 * For now, we only support "." and ".*" as non-literal metacharacters,
 * because that's all that anyone has found use for in resultmap.  This
 * code could be extended if more functionality is needed.
 */
static bool
string_matches_pattern(const char *str, const char *pattern)
{// #lizard forgives
    while (*str && *pattern)
    {
        if (*pattern == '.' && pattern[1] == '*')
        {
            pattern += 2;
            /* Trailing .* matches everything. */
            if (*pattern == '\0')
                return true;

            /*
             * Otherwise, scan for a text position at which we can match the
             * rest of the pattern.
             */
            while (*str)
            {
                /*
                 * Optimization to prevent most recursion: don't recurse
                 * unless first pattern char might match this text char.
                 */
                if (*str == *pattern || *pattern == '.')
                {
                    if (string_matches_pattern(str, pattern))
                        return true;
                }

                str++;
            }

            /*
             * End of text with no match.
             */
            return false;
        }
        else if (*pattern != '.' && *str != *pattern)
        {
            /*
             * Not the single-character wildcard and no explicit match? Then
             * time to quit...
             */
            return false;
        }

        str++;
        pattern++;
    }

    if (*pattern == '\0')
        return true;            /* end of pattern, so declare match */

    /* End of input string.  Do we have matching pattern remaining? */
    while (*pattern == '.' && pattern[1] == '*')
        pattern += 2;
    if (*pattern == '\0')
        return true;            /* end of pattern, so declare match */

    return false;
}

/*
 * Replace all occurrences of a string in a string with a different string.
 * NOTE: Assumes there is enough room in the target buffer!
 */
void
replace_string(char *string, char *replace, char *replacement)
{
    char       *ptr;

    while ((ptr = strstr(string, replace)) != NULL)
    {
        char       *dup = pg_strdup(string);

        strlcpy(string, dup, ptr - string + 1);
        strcat(string, replacement);
        strcat(string, dup + (ptr - string) + strlen(replace));
        free(dup);
    }
}

/*
 * Convert *.source found in the "source" directory, replacing certain tokens
 * in the file contents with their intended values, and put the resulting files
 * in the "dest" directory, replacing the ".source" prefix in their names with
 * the given suffix.
 */
static void
convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, char *suffix)
{// #lizard forgives
    char        testtablespace[MAXPGPATH];
    char        indir[MAXPGPATH];
    struct stat st;
    int            ret;
    char      **name;
    char      **names;
    int            count = 0;

    snprintf(indir, MAXPGPATH, "%s/%s", inputdir, source_subdir);

    /* Check that indir actually exists and is a directory */
    ret = stat(indir, &st);
    if (ret != 0 || !S_ISDIR(st.st_mode))
    {
        /*
         * No warning, to avoid noise in tests that do not have these
         * directories; for example, ecpg, contrib and src/pl.
         */
        return;
    }

    names = pgfnames(indir);
    if (!names)
        /* Error logged in pgfnames */
        exit(2);

    snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);

#ifdef WIN32

    /*
     * On Windows only, clean out the test tablespace dir, or create it if it
     * doesn't exist.  On other platforms we expect the Makefile to take care
     * of that.  (We don't migrate that functionality in here because it'd be
     * harder to cope with platform-specific issues such as SELinux.)
     *
     * XXX it would be better if pg_regress.c had nothing at all to do with
     * testtablespace, and this were handled by a .BAT file or similar on
     * Windows.  See pgsql-hackers discussion of 2008-01-18.
     */
    if (directory_exists(testtablespace))
        if (!rmtree(testtablespace, true))
        {
            fprintf(stderr, _("\n%s: could not remove test tablespace \"%s\"\n"),
                    progname, testtablespace);
            exit(2);
        }
    make_directory(testtablespace);
#endif

    /* finally loop on each file and do the replacement */
    for (name = names; *name; name++)
    {
        char        srcfile[MAXPGPATH];
        char        destfile[MAXPGPATH];
        char        prefix[MAXPGPATH];
        FILE       *infile,
                   *outfile;
        char        line[1024];

        /* reject filenames not finishing in ".source" */
        if (strlen(*name) < 8)
            continue;
        if (strcmp(*name + strlen(*name) - 7, ".source") != 0)
            continue;

        count++;

        /* build the full actual paths to open */
        snprintf(prefix, strlen(*name) - 6, "%s", *name);
        snprintf(srcfile, MAXPGPATH, "%s/%s", indir, *name);
        snprintf(destfile, MAXPGPATH, "%s/%s/%s.%s", dest_dir, dest_subdir,
                 prefix, suffix);

        infile = fopen(srcfile, "r");
        if (!infile)
        {
            fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
                    progname, srcfile, strerror(errno));
            exit(2);
        }
        outfile = fopen(destfile, "w");
        if (!outfile)
        {
            fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
                    progname, destfile, strerror(errno));
            exit(2);
        }
        while (fgets(line, sizeof(line), infile))
        {
            replace_string(line, "@abs_srcdir@", inputdir);
            replace_string(line, "@abs_builddir@", outputdir);
            replace_string(line, "@testtablespace@", testtablespace);
            replace_string(line, "@libdir@", dlpath);
            replace_string(line, "@DLSUFFIX@", DLSUFFIX);
            fputs(line, outfile);
        }
        fclose(infile);
        fclose(outfile);
    }

    /*
     * If we didn't process any files, complain because it probably means
     * somebody neglected to pass the needed --inputdir argument.
     */
    if (count <= 0)
    {
        fprintf(stderr, _("%s: no *.source files found in \"%s\"\n"),
                progname, indir);
        exit(2);
    }

    pgfnames_cleanup(names);
}

/* Create the .sql and .out files from the .source files, if any */
static void
convert_sourcefiles(void)
{
    convert_sourcefiles_in("input", outputdir, "sql", "sql");
    convert_sourcefiles_in("output", outputdir, "expected", "out");
}

/*
 * Scan resultmap file to find which platform-specific expected files to use.
 *
 * The format of each line of the file is
 *           testname/hostplatformpattern=substitutefile
 * where the hostplatformpattern is evaluated per the rules of expr(1),
 * namely, it is a standard regular expression with an implicit ^ at the start.
 * (We currently support only a very limited subset of regular expressions,
 * see string_matches_pattern() above.)  What hostplatformpattern will be
 * matched against is the config.guess output.  (In the shell-script version,
 * we also provided an indication of whether gcc or another compiler was in
 * use, but that facility isn't used anymore.)
 */
static void
load_resultmap(void)
{// #lizard forgives
    char        buf[MAXPGPATH];
    FILE       *f;

    /* scan the file ... */
    snprintf(buf, sizeof(buf), "%s/resultmap", inputdir);
    f = fopen(buf, "r");
    if (!f)
    {
        /* OK if it doesn't exist, else complain */
        if (errno == ENOENT)
            return;
        fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
                progname, buf, strerror(errno));
        exit(2);
    }

    while (fgets(buf, sizeof(buf), f))
    {
        char       *platform;
        char       *file_type;
        char       *expected;
        int            i;

        /* strip trailing whitespace, especially the newline */
        i = strlen(buf);
        while (i > 0 && isspace((unsigned char) buf[i - 1]))
            buf[--i] = '\0';

        /* parse out the line fields */
        file_type = strchr(buf, ':');
        if (!file_type)
        {
            fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
                    buf);
            exit(2);
        }
        *file_type++ = '\0';

        platform = strchr(file_type, ':');
        if (!platform)
        {
            fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
                    buf);
            exit(2);
        }
        *platform++ = '\0';
        expected = strchr(platform, '=');
        if (!expected)
        {
            fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
                    buf);
            exit(2);
        }
        *expected++ = '\0';

        /*
         * if it's for current platform, save it in resultmap list. Note: by
         * adding at the front of the list, we ensure that in ambiguous cases,
         * the last match in the resultmap file is used. This mimics the
         * behavior of the old shell script.
         */
        if (string_matches_pattern(host_platform, platform))
        {
            _resultmap *entry = pg_malloc(sizeof(_resultmap));

            entry->test = pg_strdup(buf);
            entry->type = pg_strdup(file_type);
            entry->resultfile = pg_strdup(expected);
            entry->next = resultmap;
            resultmap = entry;
        }
    }
    fclose(f);
}

/*
 * Check in resultmap if we should be looking at a different file
 */
static
const char *
get_expectfile(const char *testname, const char *file)
{
    char       *file_type;
    _resultmap *rm;

    /*
     * Determine the file type from the file name. This is just what is
     * following the last dot in the file name.
     */
    if (!file || !(file_type = strrchr(file, '.')))
        return NULL;

    file_type++;

    for (rm = resultmap; rm != NULL; rm = rm->next)
    {
        if (strcmp(testname, rm->test) == 0 && strcmp(file_type, rm->type) == 0)
        {
            return rm->resultfile;
        }
    }

    return NULL;
}

#ifndef PGXC
/*
 * Handy subroutine for setting an environment variable "var" to "val"
 */
static void
doputenv(const char *var, const char *val)
{
    char       *s;

    s = psprintf("%s=%s", var, val);
    putenv(s);
}
#endif

/*
 * Prepare environment variables for running regression tests
 */
static void
initialize_environment(void)
{// #lizard forgives
    putenv("PGAPPNAME=pg_regress");

#ifndef _PG_REGRESS_    
    if (nolocale)
    {
        /*
         * Clear out any non-C locale settings
         */
		unsetenv("LC_ALL");
        unsetenv("LC_COLLATE");
        unsetenv("LC_CTYPE");
        unsetenv("LC_MONETARY");
        unsetenv("LC_NUMERIC");
        unsetenv("LC_TIME");
        unsetenv("LANG");

        /*
         * Most platforms have adopted the POSIX locale as their
         * implementation-defined default locale.  Exceptions include native
         * Windows, macOS with --enable-nls, and Cygwin with --enable-nls.
         * (Use of --enable-nls matters because libintl replaces setlocale().)
         * Also, PostgreSQL does not support macOS with locale environment
         * variables unset; see PostmasterMain().
         */
#if defined(WIN32) || defined(__CYGWIN__) || defined(__darwin__)
            putenv("LANG=C");
#endif
        }

        /*
         * Set translation-related settings to English; otherwise psql will
         * produce translated messages and produce diffs.  (XXX If we ever support
         * translation of pg_regress, this needs to be moved elsewhere, where psql
         * is actually called.)
         */
        unsetenv("LANGUAGE");
        unsetenv("LC_ALL");
        putenv("LC_MESSAGES=C");

        /*
         * Set encoding as requested
         */
        if (encoding)
            doputenv("PGCLIENTENCODING", encoding);
        else
            unsetenv("PGCLIENTENCODING");
    
#else
        unsetenv("LC_ALL");
        putenv("LC_COLLATE=en_US.utf8");
        putenv("LC_CTYPE=en_US.utf8");
        putenv("LC_MONETARY=en_US.utf8");
        putenv("LC_NUMERIC=en_US.utf8");
        putenv("LC_TIME=en_US.utf8");
        putenv("LANG=en_US.utf8");
        putenv("LC_MESSAGES=en_US.utf8");
        doputenv("PGCLIENTENCODING", "utf8");
        nolocale = false;
#endif
    

    /*
     * Set timezone and datestyle for datetime-related tests
     */
    putenv("PGTZ=PST8PDT");
    putenv("PGDATESTYLE=Postgres, MDY");

    /*
     * Likewise set intervalstyle to ensure consistent results.  This is a bit
     * more painful because we must use PGOPTIONS, and we want to preserve the
     * user's ability to set other variables through that.
     */
    {
        const char *my_pgoptions = "-c intervalstyle=postgres_verbose";
        const char *old_pgoptions = getenv("PGOPTIONS");
        char       *new_pgoptions;

        if (!old_pgoptions)
            old_pgoptions = "";
        new_pgoptions = psprintf("PGOPTIONS=%s %s",
                                 old_pgoptions, my_pgoptions);
        putenv(new_pgoptions);
    }

    if (temp_instance)
    {
        /*
         * Clear out any environment vars that might cause psql to connect to
         * the wrong postmaster, or otherwise behave in nondefault ways. (Note
         * we also use psql's -X switch consistently, so that ~/.psqlrc files
         * won't mess things up.)  Also, set PGPORT to the temp port, and set
         * PGHOST depending on whether we are using TCP or Unix sockets.
         */
        unsetenv("PGDATABASE");
        unsetenv("PGUSER");
        unsetenv("PGSERVICE");
        unsetenv("PGSSLMODE");
        unsetenv("PGREQUIRESSL");
        unsetenv("PGCONNECT_TIMEOUT");
        unsetenv("PGDATA");
#ifdef HAVE_UNIX_SOCKETS
        if (hostname != NULL)
            doputenv("PGHOST", hostname);
        else
        {
            sockdir = getenv("PG_REGRESS_SOCK_DIR");
            if (!sockdir)
                sockdir = make_temp_sockdir();
            doputenv("PGHOST", sockdir);
        }
#else
        Assert(hostname != NULL);
        doputenv("PGHOST", hostname);
#endif
        unsetenv("PGHOSTADDR");
        if (port != -1)
        {
            char        s[16];

            sprintf(s, "%d", port);
            doputenv("PGPORT", s);
        }
    }
    else
    {
        const char *pghost;
        const char *pgport;

        /*
         * When testing an existing install, we honor existing environment
         * variables, except if they're overridden by command line options.
         */
        if (hostname != NULL)
        {
            doputenv("PGHOST", hostname);
            unsetenv("PGHOSTADDR");
        }
        if (port != -1)
        {
            char        s[16];

            sprintf(s, "%d", port);
            doputenv("PGPORT", s);
        }
        if (user != NULL)
            doputenv("PGUSER", user);

        /*
         * Report what we're connecting to
         */
        pghost = getenv("PGHOST");
        pgport = getenv("PGPORT");
#ifndef HAVE_UNIX_SOCKETS
        if (!pghost)
            pghost = "localhost";
#endif

        if (pghost && pgport)
            printf(_("(using postmaster on %s, port %s)\n"), pghost, pgport);
        if (pghost && !pgport)
            printf(_("(using postmaster on %s, default port)\n"), pghost);
        if (!pghost && pgport)
            printf(_("(using postmaster on Unix socket, port %s)\n"), pgport);
        if (!pghost && !pgport)
            printf(_("(using postmaster on Unix socket, default port)\n"));
    }

    convert_sourcefiles();
    load_resultmap();
}

pg_attribute_unused()
static const char *
fmtHba(const char *raw)
{
    static char *ret;
    const char *rp;
    char       *wp;

    wp = ret = realloc(ret, 3 + strlen(raw) * 2);

    *wp++ = '"';
    for (rp = raw; *rp; rp++)
    {
        if (*rp == '"')
            *wp++ = '"';
        *wp++ = *rp;
    }
    *wp++ = '"';
    *wp++ = '\0';

    return ret;
}

#ifdef ENABLE_SSPI
/*
 * Get account and domain/realm names for the current user.  This is based on
 * pg_SSPI_recvauth().  The returned strings use static storage.
 */
static void
current_windows_user(const char **acct, const char **dom)
{
    static char accountname[MAXPGPATH];
    static char domainname[MAXPGPATH];
    HANDLE        token;
    TOKEN_USER *tokenuser;
    DWORD        retlen;
    DWORD        accountnamesize = sizeof(accountname);
    DWORD        domainnamesize = sizeof(domainname);
    SID_NAME_USE accountnameuse;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token))
    {
        fprintf(stderr,
                _("%s: could not open process token: error code %lu\n"),
                progname, GetLastError());
        exit(2);
    }

    if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
    {
        fprintf(stderr,
                _("%s: could not get token information buffer size: error code %lu\n"),
                progname, GetLastError());
        exit(2);
    }
    tokenuser = pg_malloc(retlen);
    if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
    {
        fprintf(stderr,
                _("%s: could not get token information: error code %lu\n"),
                progname, GetLastError());
        exit(2);
    }

    if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
                          domainname, &domainnamesize, &accountnameuse))
    {
        fprintf(stderr,
                _("%s: could not look up account SID: error code %lu\n"),
                progname, GetLastError());
        exit(2);
    }

    free(tokenuser);

    *acct = accountname;
    *dom = domainname;
}

/*
 * Rewrite pg_hba.conf and pg_ident.conf to use SSPI authentication.  Permit
 * the current OS user to authenticate as the bootstrap superuser and as any
 * user named in a --create-role option.
 */
static void
config_sspi_auth(const char *pgdata)
{// #lizard forgives
    const char *accountname,
               *domainname;
    const char *username;
    char       *errstr;
    bool        have_ipv6;
    char        fname[MAXPGPATH];
    int            res;
    FILE       *hba,
               *ident;
    _stringlist *sl;

    /*
     * "username", the initdb-chosen bootstrap superuser name, may always
     * match "accountname", the value SSPI authentication discovers.  The
     * underlying system functions do not clearly guarantee that.
     */
    current_windows_user(&accountname, &domainname);
    username = get_user_name(&errstr);
    if (username == NULL)
    {
        fprintf(stderr, "%s: %s\n", progname, errstr);
        exit(2);
    }

    /*
     * Like initdb.c:setup_config(), determine whether the platform recognizes
     * ::1 (IPv6 loopback) as a numeric host address string.
     */
    {
        struct addrinfo *gai_result;
        struct addrinfo hints;
        WSADATA        wsaData;

        hints.ai_flags = AI_NUMERICHOST;
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = 0;
        hints.ai_protocol = 0;
        hints.ai_addrlen = 0;
        hints.ai_canonname = NULL;
        hints.ai_addr = NULL;
        hints.ai_next = NULL;

        have_ipv6 = (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0 &&
                     getaddrinfo("::1", NULL, &hints, &gai_result) == 0);
    }

    /* Check a Write outcome and report any error. */
#define CW(cond)    \
    do { \
        if (!(cond)) \
        { \
            fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), \
                    progname, fname, strerror(errno)); \
            exit(2); \
        } \
    } while (0)

    res = snprintf(fname, sizeof(fname), "%s/pg_hba.conf", pgdata);
    if (res < 0 || res >= sizeof(fname) - 1)
    {
        /*
         * Truncating this name is a fatal error, because we must not fail to
         * overwrite an original trust-authentication pg_hba.conf.
         */
        fprintf(stderr, _("%s: directory name too long\n"), progname);
        exit(2);
    }
    hba = fopen(fname, "w");
    if (hba == NULL)
    {
        fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
                progname, fname, strerror(errno));
        exit(2);
    }
    CW(fputs("# Configuration written by config_sspi_auth()\n", hba) >= 0);
    CW(fputs("host all all 127.0.0.1/32  sspi include_realm=1 map=regress\n",
             hba) >= 0);
    if (have_ipv6)
        CW(fputs("host all all ::1/128  sspi include_realm=1 map=regress\n",
                 hba) >= 0);
    CW(fclose(hba) == 0);

    snprintf(fname, sizeof(fname), "%s/pg_ident.conf", pgdata);
    ident = fopen(fname, "w");
    if (ident == NULL)
    {
        fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
                progname, fname, strerror(errno));
        exit(2);
    }
    CW(fputs("# Configuration written by config_sspi_auth()\n", ident) >= 0);

    /*
     * Double-quote for the benefit of account names containing whitespace or
     * '#'.  Windows forbids the double-quote character itself, so don't
     * bother escaping embedded double-quote characters.
     */
    CW(fprintf(ident, "regress  \"%s@%s\"  %s\n",
               accountname, domainname, fmtHba(username)) >= 0);
    for (sl = extraroles; sl; sl = sl->next)
        CW(fprintf(ident, "regress  \"%s@%s\"  %s\n",
                   accountname, domainname, fmtHba(sl->str)) >= 0);
    CW(fclose(ident) == 0);
}
#endif

/*
 * Issue a command via psql, connecting to the specified database
 *
 * Since we use system(), this doesn't return until the operation finishes
 */
static void
psql_command(const char *database, const char *query,...)
{
    char        query_formatted[1024];
    char        query_escaped[2048];
    char        psql_cmd[MAXPGPATH + 2048];
    va_list        args;
    char       *s;
    char       *d;

    /* Generate the query with insertion of sprintf arguments */
    va_start(args, query);
    vsnprintf(query_formatted, sizeof(query_formatted), query, args);
    va_end(args);

    /* Now escape any shell double-quote metacharacters */
    d = query_escaped;
    for (s = query_formatted; *s; s++)
    {
        if (strchr("\\\"$`", *s))
            *d++ = '\\';
        *d++ = *s;
    }
    *d = '\0';

    /* And now we can build and execute the shell command */
    snprintf(psql_cmd, sizeof(psql_cmd),
             "\"%s%spsql\" -X -c \"%s\" \"%s\"",
             bindir ? bindir : "",
             bindir ? "/" : "",
             query_escaped,
             database);

    if (system(psql_cmd) != 0)
    {
        /* psql probably already reported the error */
        fprintf(stderr, _("command failed: %s\n"), psql_cmd);
        exit(2);
    }
}

/*
 * Spawn a process to execute the given shell command; don't wait for it
 *
 * Returns the process ID (or HANDLE) so we can wait for it later
 */
PID_TYPE
spawn_process(const char *cmdline)
{
#ifndef WIN32
    pid_t        pid;

    /*
     * Must flush I/O buffers before fork.  Ideally we'd use fflush(NULL) here
     * ... does anyone still care about systems where that doesn't work?
     */
    fflush(stdout);
    fflush(stderr);
    if (logfile)
        fflush(logfile);

    pid = fork();
    if (pid == -1)
    {
        fprintf(stderr, _("%s: could not fork: %s\n"),
                progname, strerror(errno));
        exit(2);
    }
    if (pid == 0)
    {
        /*
         * In child
         *
         * Instead of using system(), exec the shell directly, and tell it to
         * "exec" the command too.  This saves two useless processes per
         * parallel test case.
         */
        char       *cmdline2;

        cmdline2 = psprintf("exec %s", cmdline);
        execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
        fprintf(stderr, _("%s: could not exec \"%s\": %s\n"),
                progname, shellprog, strerror(errno));
        _exit(1);                /* not exit() here... */
    }
    /* in parent */
    return pid;
#else
    PROCESS_INFORMATION pi;
    char       *cmdline2;
    HANDLE        restrictedToken;

    memset(&pi, 0, sizeof(pi));
    cmdline2 = psprintf("cmd /c \"%s\"", cmdline);

    if ((restrictedToken =
         CreateRestrictedProcess(cmdline2, &pi, progname)) == 0)
        exit(2);

    CloseHandle(pi.hThread);
    return pi.hProcess;
#endif
}

/*
 * Count bytes in file
 */
static long
file_size(const char *file)
{
    long        r;
    FILE       *f = fopen(file, "r");

    if (!f)
    {
        fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
                progname, file, strerror(errno));
        return -1;
    }
    fseek(f, 0, SEEK_END);
    r = ftell(f);
    fclose(f);
    return r;
}

/*
 * Count lines in file
 */
static int
file_line_count(const char *file)
{
    int            c;
    int            l = 0;
    FILE       *f = fopen(file, "r");

    if (!f)
    {
        fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
                progname, file, strerror(errno));
        return -1;
    }
    while ((c = fgetc(f)) != EOF)
    {
        if (c == '\n')
            l++;
    }
    fclose(f);
    return l;
}

bool
file_exists(const char *file)
{
    FILE       *f = fopen(file, "r");

    if (!f)
        return false;
    fclose(f);
    return true;
}

static bool
directory_exists(const char *dir)
{
    struct stat st;

    if (stat(dir, &st) != 0)
        return false;
    if (S_ISDIR(st.st_mode))
        return true;
    return false;
}

/* Create a directory */
static void
make_directory(const char *dir)
{
    if (mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO) < 0)
    {
        fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
                progname, dir, strerror(errno));
        exit(2);
    }
}

/*
 * In: filename.ext, Return: filename_i.ext, where 0 < i <= 9
 */
static char *
get_alternative_expectfile(const char *expectfile, int i)
{
    char       *last_dot;
    int            ssize = strlen(expectfile) + 2 + 1;
    char       *tmp;
    char       *s;

    if (!(tmp = (char *) malloc(ssize)))
        return NULL;

    if (!(s = (char *) malloc(ssize)))
    {
        free(tmp);
        return NULL;
    }

    strcpy(tmp, expectfile);
    last_dot = strrchr(tmp, '.');
    if (!last_dot)
    {
        free(tmp);
        free(s);
        return NULL;
    }
    *last_dot = '\0';
    snprintf(s, ssize, "%s_%d.%s", tmp, i, last_dot + 1);
    free(tmp);
    return s;
}

/*
 * Run a "diff" command and also check that it didn't crash
 */
static int
run_diff(const char *cmd, const char *filename)
{
    int            r;

    r = system(cmd);
    if (!WIFEXITED(r) || WEXITSTATUS(r) > 1)
    {
        fprintf(stderr, _("diff command failed with status %d: %s\n"), r, cmd);
        exit(2);
    }
#ifdef WIN32

    /*
     * On WIN32, if the 'diff' command cannot be found, system() returns 1,
     * but produces nothing to stdout, so we check for that here.
     */
    if (WEXITSTATUS(r) == 1 && file_size(filename) <= 0)
    {
        fprintf(stderr, _("diff command not found: %s\n"), cmd);
        exit(2);
    }
#endif

    return WEXITSTATUS(r);
}

/*
 * Check the actual result file for the given test against expected results
 *
 * Returns true if different (failure), false if correct match found.
 * In the true case, the diff is appended to the diffs file.
 */
static bool
results_differ(const char *testname, const char *resultsfile, const char *default_expectfile)
{// #lizard forgives
    char        expectfile[MAXPGPATH];
    char        diff[MAXPGPATH];
    char        cmd[MAXPGPATH * 3];
    char        best_expect_file[MAXPGPATH];
    FILE       *difffile;
    int            best_line_count;
    int            i;
    int            l;
    const char *platform_expectfile;

    /*
     * We can pass either the resultsfile or the expectfile, they should have
     * the same type (filename.type) anyway.
     */
    platform_expectfile = get_expectfile(testname, resultsfile);

    strlcpy(expectfile, default_expectfile, sizeof(expectfile));
    if (platform_expectfile)
    {
        /*
         * Replace everything after the last slash in expectfile with what the
         * platform_expectfile contains.
         */
        char       *p = strrchr(expectfile, '/');

        if (p)
            strcpy(++p, platform_expectfile);
    }

    /* Name to use for temporary diff file */
    snprintf(diff, sizeof(diff), "%s.diff", resultsfile);

    /* OK, run the diff */
    snprintf(cmd, sizeof(cmd),
             "diff %s \"%s\" \"%s\" > \"%s\"",
             basic_diff_opts, expectfile, resultsfile, diff);

    /* Is the diff file empty? */
    if (run_diff(cmd, diff) == 0)
    {
        unlink(diff);
        return false;
    }

    /* There may be secondary comparison files that match better */
    best_line_count = file_line_count(diff);
    strcpy(best_expect_file, expectfile);

    for (i = 0; i <= 9; i++)
    {
        char       *alt_expectfile;

        alt_expectfile = get_alternative_expectfile(expectfile, i);
        if (!alt_expectfile)
        {
            fprintf(stderr, _("Unable to check secondary comparison files: %s\n"),
                    strerror(errno));
            exit(2);
        }

        if (!file_exists(alt_expectfile))
        {
            free(alt_expectfile);
            continue;
        }

        snprintf(cmd, sizeof(cmd),
                 "diff %s \"%s\" \"%s\" > \"%s\"",
                 basic_diff_opts, alt_expectfile, resultsfile, diff);

        if (run_diff(cmd, diff) == 0)
        {
            unlink(diff);
            free(alt_expectfile);
            return false;
        }

        l = file_line_count(diff);
        if (l < best_line_count)
        {
            /* This diff was a better match than the last one */
            best_line_count = l;
            strlcpy(best_expect_file, alt_expectfile, sizeof(best_expect_file));
        }
        free(alt_expectfile);
    }

    /*
     * fall back on the canonical results file if we haven't tried it yet and
     * haven't found a complete match yet.
     */

    if (platform_expectfile)
    {
        snprintf(cmd, sizeof(cmd),
                 "diff %s \"%s\" \"%s\" > \"%s\"",
                 basic_diff_opts, default_expectfile, resultsfile, diff);

        if (run_diff(cmd, diff) == 0)
        {
            /* No diff = no changes = good */
            unlink(diff);
            return false;
        }

        l = file_line_count(diff);
        if (l < best_line_count)
        {
            /* This diff was a better match than the last one */
            best_line_count = l;
            strlcpy(best_expect_file, default_expectfile, sizeof(best_expect_file));
        }
    }

    /*
     * Use the best comparison file to generate the "pretty" diff, which we
     * append to the diffs summary file.
     */
    snprintf(cmd, sizeof(cmd),
             "diff %s \"%s\" \"%s\" >> \"%s\"",
             pretty_diff_opts, best_expect_file, resultsfile, difffilename);
    run_diff(cmd, difffilename);

    /* And append a separator */
    difffile = fopen(difffilename, "a");
    if (difffile)
    {
        fprintf(difffile,
                "\n======================================================================\n\n");
        fclose(difffile);
    }

    unlink(diff);
    return true;
}

/*
 * Wait for specified subprocesses to finish, and return their exit
 * statuses into statuses[]
 *
 * If names isn't NULL, print each subprocess's name as it finishes
 *
 * Note: it's OK to scribble on the pids array, but not on the names array
 */
static void
wait_for_tests(PID_TYPE * pids, int *statuses, char **names, int num_tests)
{// #lizard forgives
    int            tests_left;
    int            i;

#ifdef WIN32
    PID_TYPE   *active_pids = pg_malloc(num_tests * sizeof(PID_TYPE));

    memcpy(active_pids, pids, num_tests * sizeof(PID_TYPE));
#endif

    tests_left = num_tests;
    while (tests_left > 0)
    {
        PID_TYPE    p;

#ifndef WIN32
        int            exit_status;

        p = wait(&exit_status);

        if (p == INVALID_PID)
        {
            fprintf(stderr, _("failed to wait for subprocesses: %s\n"),
                    strerror(errno));
            exit(2);
        }
#else
        DWORD        exit_status;
        int            r;

        r = WaitForMultipleObjects(tests_left, active_pids, FALSE, INFINITE);
        if (r < WAIT_OBJECT_0 || r >= WAIT_OBJECT_0 + tests_left)
        {
            fprintf(stderr, _("failed to wait for subprocesses: error code %lu\n"),
                    GetLastError());
            exit(2);
        }
        p = active_pids[r - WAIT_OBJECT_0];
        /* compact the active_pids array */
        active_pids[r - WAIT_OBJECT_0] = active_pids[tests_left - 1];
#endif                            /* WIN32 */

        for (i = 0; i < num_tests; i++)
        {
            if (p == pids[i])
            {
#ifdef WIN32
                GetExitCodeProcess(pids[i], &exit_status);
                CloseHandle(pids[i]);
#endif
                pids[i] = INVALID_PID;
                statuses[i] = (int) exit_status;
                if (names)
                    status(" %s", names[i]);
                tests_left--;
                break;
            }
        }
    }

#ifdef WIN32
    free(active_pids);
#endif
}

/*
 * report nonzero exit code from a test process
 */
static void
log_child_failure(int exitstatus)
{
    if (WIFEXITED(exitstatus))
        status(_(" (test process exited with exit code %d)"),
               WEXITSTATUS(exitstatus));
    else if (WIFSIGNALED(exitstatus))
    {
#if defined(WIN32)
        status(_(" (test process was terminated by exception 0x%X)"),
               WTERMSIG(exitstatus));
#elif defined(HAVE_DECL_SYS_SIGLIST) && HAVE_DECL_SYS_SIGLIST
        status(_(" (test process was terminated by signal %d: %s)"),
               WTERMSIG(exitstatus),
               WTERMSIG(exitstatus) < NSIG ?
               sys_siglist[WTERMSIG(exitstatus)] : "(unknown))");
#else
        status(_(" (test process was terminated by signal %d)"),
               WTERMSIG(exitstatus));
#endif
    }
    else
        status(_(" (test process exited with unrecognized status %d)"),
               exitstatus);
}

/*
 * Run all the tests specified in one schedule file
 */
static void
run_schedule(const char *schedule, test_function tfunc)
{// #lizard forgives
#define MAX_PARALLEL_TESTS 100
    char       *tests[MAX_PARALLEL_TESTS];
    _stringlist *resultfiles[MAX_PARALLEL_TESTS];
    _stringlist *expectfiles[MAX_PARALLEL_TESTS];
    _stringlist *tags[MAX_PARALLEL_TESTS];
    PID_TYPE    pids[MAX_PARALLEL_TESTS];
    int            statuses[MAX_PARALLEL_TESTS];
    _stringlist *ignorelist = NULL;
    char        scbuf[1024];
    FILE       *scf;
    int            line_num = 0;

    memset(resultfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
    memset(expectfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
    memset(tags, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);

    scf = fopen(schedule, "r");
    if (!scf)
    {
        fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
                progname, schedule, strerror(errno));
        exit(2);
    }

    while (fgets(scbuf, sizeof(scbuf), scf))
    {
        char       *test = NULL;
        char       *c;
        int            num_tests;
        bool        inword;
        int            i;

        line_num++;

        for (i = 0; i < MAX_PARALLEL_TESTS; i++)
        {
            if (resultfiles[i] == NULL)
                break;
            free_stringlist(&resultfiles[i]);
            free_stringlist(&expectfiles[i]);
            free_stringlist(&tags[i]);
        }

        /* strip trailing whitespace, especially the newline */
        i = strlen(scbuf);
        while (i > 0 && isspace((unsigned char) scbuf[i - 1]))
            scbuf[--i] = '\0';

        if (scbuf[0] == '\0' || scbuf[0] == '#')
            continue;
        if (strncmp(scbuf, "test: ", 6) == 0)
            test = scbuf + 6;
        else if (strncmp(scbuf, "ignore: ", 8) == 0)
        {
            c = scbuf + 8;
            while (*c && isspace((unsigned char) *c))
                c++;
            add_stringlist_item(&ignorelist, c);

            /*
             * Note: ignore: lines do not run the test, they just say that
             * failure of this test when run later on is to be ignored. A bit
             * odd but that's how the shell-script version did it.
             */
            continue;
        }
        else
        {
            fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n"),
                    schedule, line_num, scbuf);
            exit(2);
        }

        num_tests = 0;
        inword = false;
        for (c = test; *c; c++)
        {
            if (isspace((unsigned char) *c))
            {
                *c = '\0';
                inword = false;
            }
            else if (!inword)
            {
                if (num_tests >= MAX_PARALLEL_TESTS)
                {
                    /* can't print scbuf here, it's already been trashed */
                    fprintf(stderr, _("too many parallel tests in schedule file \"%s\", line %d\n"),
                            schedule, line_num);
                    exit(2);
                }
                tests[num_tests] = c;
                num_tests++;
                inword = true;
            }
        }

        if (num_tests == 0)
        {
            fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n"),
                    schedule, line_num, scbuf);
            exit(2);
        }

        if (num_tests == 1)
        {
            status(_("test %-24s ... "), tests[0]);
            pids[0] = (tfunc) (tests[0], &resultfiles[0], &expectfiles[0], &tags[0]);
            wait_for_tests(pids, statuses, NULL, 1);
            /* status line is finished below */
        }
        else if (max_connections > 0 && max_connections < num_tests)
        {
            int            oldest = 0;

            status(_("parallel group (%d tests, in groups of %d): "),
                   num_tests, max_connections);
            for (i = 0; i < num_tests; i++)
            {
                if (i - oldest >= max_connections)
                {
                    wait_for_tests(pids + oldest, statuses + oldest,
                                   tests + oldest, i - oldest);
                    oldest = i;
                }
                pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
            }
            wait_for_tests(pids + oldest, statuses + oldest,
                           tests + oldest, i - oldest);
            status_end();
        }
        else
        {
            status(_("parallel group (%d tests): "), num_tests);
            for (i = 0; i < num_tests; i++)
            {
                pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
            }
            wait_for_tests(pids, statuses, tests, num_tests);
            status_end();
        }

        /* Check results for all tests */
        for (i = 0; i < num_tests; i++)
        {
            _stringlist *rl,
                       *el,
                       *tl;
            bool        differ = false;

            if (num_tests > 1)
                status(_("     %-24s ... "), tests[i]);

            /*
             * Advance over all three lists simultaneously.
             *
             * Compare resultfiles[j] with expectfiles[j] always. Tags are
             * optional but if there are tags, the tag list has the same
             * length as the other two lists.
             */
            for (rl = resultfiles[i], el = expectfiles[i], tl = tags[i];
                 rl != NULL;    /* rl and el have the same length */
                 rl = rl->next, el = el->next)
            {
                bool        newdiff;

                if (tl)
                    tl = tl->next;    /* tl has the same length as rl and el if
                                     * it exists */

                newdiff = results_differ(tests[i], rl->str, el->str);
                if (newdiff && tl)
                {
                    printf("%s ", tl->str);
                }
                differ |= newdiff;
            }

            if (differ)
            {
                bool        ignore = false;
                _stringlist *sl;

                for (sl = ignorelist; sl != NULL; sl = sl->next)
                {
                    if (strcmp(tests[i], sl->str) == 0)
                    {
                        ignore = true;
                        break;
                    }
                }
                if (ignore)
                {
                    status(_("failed (ignored)"));
                    fail_ignore_count++;
                }
                else
                {
                    status(_("FAILED"));
                    fail_count++;
                }
            }
            else
            {
                status(_("ok"));
                success_count++;
            }

            if (statuses[i] != 0)
                log_child_failure(statuses[i]);

            status_end();
        }
    }

    free_stringlist(&ignorelist);

    fclose(scf);
}

/*
 * Run a single test
 */
static void
run_single_test(const char *test, test_function tfunc)
{
    PID_TYPE    pid;
    int            exit_status;
    _stringlist *resultfiles = NULL;
    _stringlist *expectfiles = NULL;
    _stringlist *tags = NULL;
    _stringlist *rl,
               *el,
               *tl;
    bool        differ = false;

    status(_("test %-24s ... "), test);
    pid = (tfunc) (test, &resultfiles, &expectfiles, &tags);
    wait_for_tests(&pid, &exit_status, NULL, 1);

    /*
     * Advance over all three lists simultaneously.
     *
     * Compare resultfiles[j] with expectfiles[j] always. Tags are optional
     * but if there are tags, the tag list has the same length as the other
     * two lists.
     */
    for (rl = resultfiles, el = expectfiles, tl = tags;
         rl != NULL;            /* rl and el have the same length */
         rl = rl->next, el = el->next)
    {
        bool        newdiff;

        if (tl)
            tl = tl->next;        /* tl has the same length as rl and el if it
                                 * exists */

        newdiff = results_differ(test, rl->str, el->str);
        if (newdiff && tl)
        {
            printf("%s ", tl->str);
        }
        differ |= newdiff;
    }

    if (differ)
    {
        status(_("FAILED"));
        fail_count++;
    }
    else
    {
        status(_("ok"));
        success_count++;
    }

    if (exit_status != 0)
        log_child_failure(exit_status);

    status_end();
}

/*
 * Create the summary-output files (making them empty if already existing)
 */
static void
open_result_files(void)
{
    char        file[MAXPGPATH];
    FILE       *difffile;

    /* create outputdir directory if not present */
    if (!directory_exists(outputdir))
        make_directory(outputdir);

    /* create the log file (copy of running status output) */
    snprintf(file, sizeof(file), "%s/regression.out", outputdir);
    logfilename = pg_strdup(file);
    logfile = fopen(logfilename, "w");
    if (!logfile)
    {
        fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
                progname, logfilename, strerror(errno));
        exit(2);
    }

    /* create the diffs file as empty */
    snprintf(file, sizeof(file), "%s/regression.diffs", outputdir);
    difffilename = pg_strdup(file);
    difffile = fopen(difffilename, "w");
    if (!difffile)
    {
        fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
                progname, difffilename, strerror(errno));
        exit(2);
    }
    /* we don't keep the diffs file open continuously */
    fclose(difffile);

    /* also create the results directory if not present */
    snprintf(file, sizeof(file), "%s/results", outputdir);
    if (!directory_exists(file))
        make_directory(file);
}

static void
drop_database_if_exists(const char *dbname)
{
    header(_("dropping database \"%s\""), dbname);
    psql_command("postgres", "DROP DATABASE IF EXISTS \"%s\"", dbname);
}

static void
create_database(const char *dbname)
{
    _stringlist *sl;

    /*
     * We use template0 so that any installation-local cruft in template1 will
     * not mess up the tests.
     */
    header(_("creating database \"%s\""), dbname);
    if (encoding)
        psql_command("postgres", "CREATE DATABASE \"%s\" TEMPLATE=template0 ENCODING='%s'%s", dbname, encoding,
                     (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "");
    else
        psql_command("postgres", "CREATE DATABASE \"%s\" TEMPLATE=template0%s", dbname,
                     (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "");
    psql_command(dbname,
                 "ALTER DATABASE \"%s\" SET lc_messages TO 'C';"
                 "ALTER DATABASE \"%s\" SET lc_monetary TO 'C';"
                 "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';"
                 "ALTER DATABASE \"%s\" SET lc_time TO 'C';"
                 "ALTER DATABASE \"%s\" SET bytea_output TO 'hex';"
                 "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
                 dbname, dbname, dbname, dbname, dbname, dbname);

    /*
     * Install any requested procedural languages.  We use CREATE OR REPLACE
     * so that this will work whether or not the language is preinstalled.
     */
    for (sl = loadlanguage; sl != NULL; sl = sl->next)
    {
        header(_("installing %s"), sl->str);
        psql_command(dbname, "CREATE OR REPLACE LANGUAGE \"%s\"", sl->str);
    }

    /*
     * Install any requested extensions.  We use CREATE IF NOT EXISTS so that
     * this will work whether or not the extension is preinstalled.
     */
    for (sl = loadextension; sl != NULL; sl = sl->next)
    {
        header(_("installing %s"), sl->str);
        psql_command(dbname, "CREATE EXTENSION IF NOT EXISTS \"%s\"", sl->str);
    }
}

static void
drop_role_if_exists(const char *rolename)
{
    header(_("dropping role \"%s\""), rolename);
    psql_command("postgres", "DROP ROLE IF EXISTS \"%s\"", rolename);
}

static void
create_role(const char *rolename, const _stringlist *granted_dbs)
{
    header(_("creating role \"%s\""), rolename);
    psql_command("postgres", "CREATE ROLE \"%s\" WITH LOGIN", rolename);
    for (; granted_dbs != NULL; granted_dbs = granted_dbs->next)
    {
        psql_command("postgres", "GRANT ALL ON DATABASE \"%s\" TO \"%s\"",
                     granted_dbs->str, rolename);
    }
}

static void
help(void)
{
    printf(_("PostgreSQL regression test driver\n"));
    printf(_("\n"));
    printf(_("Usage:\n  %s [OPTION]... [EXTRA-TEST]...\n"), progname);
    printf(_("\n"));
    printf(_("Options:\n"));
    printf(_("  --config-auth=DATADIR     update authentication settings for DATADIR\n"));
    printf(_("  --create-role=ROLE        create the specified role before testing\n"));
    printf(_("  --dbname=DB               use database DB (default \"regression\")\n"));
    printf(_("  --debug                   turn on debug mode in programs that are run\n"));
    printf(_("  --dlpath=DIR              look for dynamic libraries in DIR\n"));
    printf(_("  --encoding=ENCODING       use ENCODING as the encoding\n"));
    printf(_("  --inputdir=DIR            take input files from DIR (default \".\")\n"));
    printf(_("  --launcher=CMD            use CMD as launcher of psql\n"));
    printf(_("  --load-extension=EXT      load the named extension before running the\n"));
    printf(_("                            tests; can appear multiple times\n"));
    printf(_("  --load-language=LANG      load the named language before running the\n"));
    printf(_("                            tests; can appear multiple times\n"));
    printf(_("  --max-connections=N       maximum number of concurrent connections\n"));
    printf(_("                            (default is 0, meaning unlimited)\n"));
    printf(_("  --outputdir=DIR           place output files in DIR (default \".\")\n"));
    printf(_("  --schedule=FILE           use test ordering schedule from FILE\n"));
    printf(_("                            (can be used multiple times to concatenate)\n"));
    printf(_("  --temp-instance=DIR       create a temporary instance in DIR\n"));
    printf(_("  --use-existing            use an existing installation\n"));
    printf(_("  --txn-test                run cases for transaction feature\n"));
    printf(_("\n"));
    printf(_("Options for \"temp-instance\" mode:\n"));
    printf(_("  --no-locale               use C locale\n"));
    printf(_("  --port=PORT               start postmaster on PORT\n"));
    printf(_("  --temp-config=FILE        append contents of FILE to temporary config\n"));
    printf(_("\n"));
    printf(_("Options for using an existing installation:\n"));
    printf(_("  --host=HOST               use postmaster running on HOST\n"));
    printf(_("  --port=PORT               use postmaster running at PORT\n"));
    printf(_("  --user=USER               connect as USER\n"));
    printf(_("\n"));
    printf(_("The exit status is 0 if all tests passed, 1 if some tests failed, and 2\n"));
    printf(_("if the tests could not be run for some reason.\n"));
    printf(_("\n"));
    printf(_("Report bugs to <pgsql-bugs@postgresql.org>.\n"));
}

int
regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc)
{// #lizard forgives
    static struct option long_options[] = {
        {"help", no_argument, NULL, 'h'},
        {"version", no_argument, NULL, 'V'},
        {"dbname", required_argument, NULL, 1},
        {"debug", no_argument, NULL, 2},
        {"inputdir", required_argument, NULL, 3},
        {"load-language", required_argument, NULL, 4},
        {"max-connections", required_argument, NULL, 5},
        {"encoding", required_argument, NULL, 6},
        {"outputdir", required_argument, NULL, 7},
        {"schedule", required_argument, NULL, 8},
        {"temp-instance", required_argument, NULL, 9},
        {"no-locale", no_argument, NULL, 10},
        {"host", required_argument, NULL, 13},
        {"port", required_argument, NULL, 14},
        {"user", required_argument, NULL, 15},
        {"bindir", required_argument, NULL, 16},
        {"dlpath", required_argument, NULL, 17},
        {"create-role", required_argument, NULL, 18},
        {"temp-config", required_argument, NULL, 19},
        {"use-existing", no_argument, NULL, 20},
        {"launcher", required_argument, NULL, 21},
        {"load-extension", required_argument, NULL, 22},
        {"txn-test", no_argument, NULL, 23},
        {"config-auth", required_argument, NULL, 24},
        {NULL, 0, NULL, 0}
    };

    _stringlist *sl;
    int            c;
    int            i;
    int            option_index;
    char        buf[MAXPGPATH * 4];

    progname = get_progname(argv[0]);
    set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress"));

    atexit(stop_postmaster);

#ifndef HAVE_UNIX_SOCKETS
    /* no unix domain sockets available, so change default */
    hostname = "localhost";
#endif

    /*
     * We call the initialization function here because that way we can set
     * default parameters and let them be overwritten by the commandline.
     */
    ifunc(argc, argv);

    if (getenv("PG_REGRESS_DIFF_OPTS"))
        pretty_diff_opts = getenv("PG_REGRESS_DIFF_OPTS");

    while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
    {
        switch (c)
        {
            case 'h':
                help();
                exit(0);
            case 'V':
                puts("pg_regress (PostgreSQL) " PG_VERSION);
                exit(0);
            case 1:

                /*
                 * If a default database was specified, we need to remove it
                 * before we add the specified one.
                 */
                free_stringlist(&dblist);
                split_to_stringlist(optarg, ",", &dblist);
                break;
            case 2:
                debug = true;
                break;
            case 3:
                inputdir = pg_strdup(optarg);
                break;
            case 4:
                add_stringlist_item(&loadlanguage, optarg);
                break;
            case 5:
                max_connections = atoi(optarg);
                break;
            case 6:
                encoding = pg_strdup(optarg);
                break;
            case 7:
                outputdir = pg_strdup(optarg);
                break;
            case 8:
                add_stringlist_item(&schedulelist, optarg);
                break;
            case 9:
                temp_instance = make_absolute_path(optarg);
                break;
            case 10:
                nolocale = true;
                break;
            case 13:
                hostname = pg_strdup(optarg);
                break;
            case 14:
                port = atoi(optarg);
                port_specified_by_user = true;
                break;
            case 15:
                user = pg_strdup(optarg);
                break;
            case 16:
                /* "--bindir=" means to use PATH */
                if (strlen(optarg))
                    bindir = pg_strdup(optarg);
                else
                    bindir = NULL;
                break;
            case 17:
                dlpath = pg_strdup(optarg);
                break;
            case 18:
                split_to_stringlist(optarg, ",", &extraroles);
                break;
            case 19:
                add_stringlist_item(&temp_configs, optarg);
                break;
            case 20:
                use_existing = true;
                break;
            case 21:
                launcher = pg_strdup(optarg);
                break;
            case 22:
                add_stringlist_item(&loadextension, optarg);
                break;
#ifdef __OPENTENBASE__                
            case 23:
                add_stringlist_item(&schedulelist, "txn_test_schedual");
                break;
#endif
            case 24:
                config_auth_datadir = pg_strdup(optarg);
                break;
            default:
                /* getopt_long already emitted a complaint */
                fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
                        progname);
                exit(2);
        }
    }

    /*
     * if we still have arguments, they are extra tests to run
     */
    while (argc - optind >= 1)
    {
        add_stringlist_item(&extra_tests, argv[optind]);
        optind++;
    }

    if (config_auth_datadir)
    {
#ifdef ENABLE_SSPI
        config_sspi_auth(config_auth_datadir);
#endif
        exit(0);
    }

    if (temp_instance && !port_specified_by_user)

        /*
         * To reduce chances of interference with parallel installations, use
         * a port number starting in the private range (49152-65535)
         * calculated from the version number.  This aids !HAVE_UNIX_SOCKETS
         * systems; elsewhere, the use of a private socket directory already
         * prevents interference.
         */
        port = 0xC000 | (PG_VERSION_NUM & 0x3FFF);

#ifdef PGXC
    /* Initialize the other port numbers, user has no control on them */
    port_coord2 = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 1;
    port_dn1 = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 2;
    port_dn2 = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 3;
    port_gtm = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 4;
    co1_pooler_port = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 5;
    co2_pooler_port = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 6;
    dn1_pooler_port = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 7;
    dn2_pooler_port = (0xC000 | (PG_VERSION_NUM & 0x3FFF)) + 8;
#endif

    inputdir = make_absolute_path(inputdir);
    outputdir = make_absolute_path(outputdir);
    dlpath = make_absolute_path(dlpath);

    /*
     * Initialization
     */
    open_result_files();

    initialize_environment();

#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
    unlimit_core_size();
#endif

    if (temp_instance)
    {
#ifndef PGXC
        FILE       *pg_conf;
#endif
        const char *env_wait;
        int            wait_seconds;

        /*
         * Prepare the temp instance
         */

        if (directory_exists(temp_instance))
        {
            header(_("removing existing temp instance"));
            if (!rmtree(temp_instance, true))
            {
                fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
                        progname, temp_instance);
                exit(2);
            }
        }

        header(_("creating temporary instance"));

        /* make the temp instance top directory */
        make_directory(temp_instance);

        /* and a directory for log files */
        snprintf(buf, sizeof(buf), "%s/log", outputdir);
        if (!directory_exists(buf))
            make_directory(buf);

        /* initdb */
        header(_("initializing database system"));
#ifdef PGXC
        /* Initialize nodes and GTM */
        initdb_node(PGXC_GTM);
        initdb_node(PGXC_COORD_1);
        initdb_node(PGXC_COORD_2);
        initdb_node(PGXC_DATANODE_1);
        initdb_node(PGXC_DATANODE_2);
#else
        snprintf(buf, sizeof(buf),
                 "\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
                 bindir ? bindir : "",
                 bindir ? "/" : "",
                 temp_instance,
                 debug ? " --debug" : "",
                 nolocale ? " --no-locale" : "",
                 outputdir);
        if (system(buf))
        {
            fprintf(stderr, _("\n%s: initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
            exit(2);
        }
#endif

        /*
         * Adjust the default postgresql.conf for regression testing. The user
         * can specify a file to be appended; in any case we expand logging
         * and set max_prepared_transactions to enable testing of prepared
         * xacts.  (Note: to reduce the probability of unexpected shmmax
         * failures, don't set max_prepared_transactions any higher than
         * actually needed by the prepared_xacts regression test.)
         */

#ifdef PGXC
        /*
         * Update configuration file of each node with user-defined options
         * and 2PC related information.
         * PGXCTODO: calculate port of GTM before setting configuration files
         */
        set_node_config_file(PGXC_COORD_1);
        set_node_config_file(PGXC_COORD_2);
        set_node_config_file(PGXC_DATANODE_1);
        set_node_config_file(PGXC_DATANODE_2);
#else
        snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_instance);
        pg_conf = fopen(buf, "a");
        if (pg_conf == NULL)
        {
            fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n"), progname, buf, strerror(errno));
            exit(2);
        }
        fputs("\n# Configuration added by pg_regress\n\n", pg_conf);
        fputs("log_autovacuum_min_duration = 0\n", pg_conf);
        fputs("log_checkpoints = on\n", pg_conf);
        fputs("log_line_prefix = '%m [%p] %q%a '\n", pg_conf);
        fputs("log_lock_waits = on\n", pg_conf);
        fputs("log_temp_files = 128kB\n", pg_conf);
        fputs("max_prepared_transactions = 2\n", pg_conf);

        for (sl = temp_configs; sl != NULL; sl = sl->next)
        {
            char       *temp_config = sl->str;
            FILE       *extra_conf;
            char        line_buf[1024];

            extra_conf = fopen(temp_config, "r");
            if (extra_conf == NULL)
            {
                fprintf(stderr, _("\n%s: could not open \"%s\" to read extra config: %s\n"), progname, temp_config, strerror(errno));
                exit(2);
            }
            while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL)
                fputs(line_buf, pg_conf);
            fclose(extra_conf);
        }

        fclose(pg_conf);
#endif

#ifdef ENABLE_SSPI

        /*
         * Since we successfully used the same buffer for the much-longer
         * "initdb" command, this can't truncate.
         */
        snprintf(buf, sizeof(buf), "%s/data", temp_instance);
        config_sspi_auth(buf);
#elif !defined(HAVE_UNIX_SOCKETS)
#error Platform has no means to secure the test installation.
#endif

#ifdef PGXC
        /* Determine port numbers for nodes */
        calculate_node_port(PGXC_COORD_1, true);
        calculate_node_port(PGXC_COORD_2, false);
        calculate_node_port(PGXC_DATANODE_1, false);
        calculate_node_port(PGXC_DATANODE_2, false);
#else
        /*
         * Check if there is a postmaster running already.
         */
        snprintf(buf2, sizeof(buf2),
                 "\"%s%spsql\" -X postgres <%s 2>%s",
                 bindir ? bindir : "",
                 bindir ? "/" : "",
                 DEVNULL, DEVNULL);

        for (i = 0; i < 16; i++)
        {
            if (system(buf2) == 0)
            {
                char        s[16];

                if (port_specified_by_user || i == 15)
                {
                    fprintf(stderr, _("port %d apparently in use\n"), port);
                    if (!port_specified_by_user)
                        fprintf(stderr, _("%s: could not determine an available port\n"), progname);
                    fprintf(stderr, _("Specify an unused port using the --port option or shut down any conflicting PostgreSQL servers.\n"));
                    exit(2);
                }

                fprintf(stderr, _("port %d apparently in use, trying %d\n"), port, port + 1);
                port++;
                sprintf(s, "%d", port);
                doputenv("PGPORT", s);
            }
            else
                break;
        }
#endif

        /*
         * Start the temp postmaster
         */
        header(_("starting postmaster"));
#ifdef PGXC
        /* Start GTM */
        start_node(PGXC_GTM, false, false);

        /* Start all the nodes */
        start_node(PGXC_COORD_1, true, false);
        start_node(PGXC_COORD_2, true, false);
        start_node(PGXC_DATANODE_1, false, false);
        start_node(PGXC_DATANODE_2, false, false);
#else
        snprintf(buf, sizeof(buf),
                 "\"%s%spostgres\" -D \"%s/data\" -F%s "
                 "-c \"listen_addresses=%s\" -k \"%s\" "
                 "> \"%s/log/postmaster.log\" 2>&1",
                 bindir ? bindir : "",
                 bindir ? "/" : "",
                 temp_instance, debug ? " -d 5" : "",
                 hostname ? hostname : "", sockdir ? sockdir : "",
                 outputdir);
        postmaster_pid = spawn_process(buf);
        if (postmaster_pid == INVALID_PID)
        {
            fprintf(stderr, _("\n%s: could not spawn postmaster: %s\n"),
                    progname, strerror(errno));
            exit(2);
        }
#endif

        /*
         * Wait till postmaster is able to accept connections; normally this
         * is only a second or so, but Cygwin is reportedly *much* slower, and
         * test builds using Valgrind or similar tools might be too.  Hence,
         * allow the default timeout of 60 seconds to be overridden from the
         * PGCTLTIMEOUT environment variable.
         */
        env_wait = getenv("PGCTLTIMEOUT");
        if (env_wait != NULL)
        {
            wait_seconds = atoi(env_wait);
            if (wait_seconds <= 0)
                wait_seconds = 60;
        }
        else
            wait_seconds = 60;

        for (i = 0; i < wait_seconds; i++)
        {

#ifdef PGXC
            /* Done if psql succeeds for each node */
            if (check_node_running(PGXC_COORD_1) &&
                check_node_running(PGXC_COORD_2) &&
                check_node_running(PGXC_DATANODE_1) &&
                check_node_running(PGXC_DATANODE_2))
                break;

            /* Check node failure */
            check_node_fail(PGXC_COORD_1);
            check_node_fail(PGXC_COORD_2);
            check_node_fail(PGXC_DATANODE_1);
            check_node_fail(PGXC_DATANODE_2);
#else
            /* Done if psql succeeds */
            if (system(buf2) == 0)
                break;

            /*
             * Fail immediately if postmaster has exited
             */
#ifndef WIN32
            if (kill(postmaster_pid, 0) != 0)
#else
            if (WaitForSingleObject(postmaster_pid, 0) == WAIT_OBJECT_0)
#endif
            {
                fprintf(stderr, _("\n%s: postmaster failed\nExamine %s/log/postmaster.log for the reason\n"), progname, outputdir);
                exit(2);
            }
#endif /* PGXC */

            pg_usleep(1000000L);
        }
        if (i >= wait_seconds)
        {
#ifdef PGXC
            /* If one node fails, all fail */
            kill_node(PGXC_COORD_1);
            kill_node(PGXC_COORD_2);
            kill_node(PGXC_DATANODE_1);
            kill_node(PGXC_DATANODE_2);
#else
            fprintf(stderr, _("\n%s: postmaster did not respond within %d seconds\nExamine %s/log/postmaster.log for the reason\n"),
                    progname, wait_seconds, outputdir);

            /*
             * If we get here, the postmaster is probably wedged somewhere in
             * startup.  Try to kill it ungracefully rather than leaving a
             * stuck postmaster that might interfere with subsequent test
             * attempts.
             */
#ifndef WIN32
            if (kill(postmaster_pid, SIGKILL) != 0 &&
                errno != ESRCH)
                fprintf(stderr, _("\n%s: could not kill failed postmaster: %s\n"),
                        progname, strerror(errno));
#else
            if (TerminateProcess(postmaster_pid, 255) == 0)
                fprintf(stderr, _("\n%s: could not kill failed postmaster: error code %lu\n"),
                        progname, GetLastError());
#endif
#endif /* PGXC */

            exit(2);
        }

        postmaster_running = true;

#ifdef _WIN64
/* need a series of two casts to convert HANDLE without compiler warning */
#define ULONGPID(x) (unsigned long) (unsigned long long) (x)
#else
#define ULONGPID(x) (unsigned long) (x)
#endif

#ifdef PGXC
        /* Print info for each node */
        printf(_("running on port %d, pooler port %d with PID %lu for Coordinator 1\n"),
               get_port_number(PGXC_COORD_1), get_pooler_port(PGXC_COORD_1), ULONGPID(get_node_pid(PGXC_COORD_1)));
        printf(_("running on port %d, pooler port %d with PID %lu for Coordinator 2\n"),
               get_port_number(PGXC_COORD_2), get_pooler_port(PGXC_COORD_2), ULONGPID(get_node_pid(PGXC_COORD_2)));
        printf(_("running on port %d with PID %lu for Datanode 1\n"),
               get_port_number(PGXC_DATANODE_1), ULONGPID(get_node_pid(PGXC_DATANODE_1)));
        printf(_("running on port %d with PID %lu for Datanode 2\n"),
               get_port_number(PGXC_DATANODE_2), ULONGPID(get_node_pid(PGXC_DATANODE_2)));
        printf(_("running on port %d with PID %lu for GTM\n"),
               get_port_number(PGXC_GTM), ULONGPID(get_node_pid(PGXC_GTM)));

        /* Postmaster is finally running, so set up connection information on Coordinators */
        setup_connection_information();
#else
        printf(_("running on port %d with PID %lu\n"),
               port, ULONGPID(postmaster_pid));
#endif
    }
    else
    {
        /*
         * Using an existing installation, so may need to get rid of
         * pre-existing database(s) and role(s)
         */
        if (!use_existing)
        {
            for (sl = dblist; sl; sl = sl->next)
                drop_database_if_exists(sl->str);
            for (sl = extraroles; sl; sl = sl->next)
                drop_role_if_exists(sl->str);
        }
    }

    /*
     * Create the test database(s) and role(s)
     */
    if (!use_existing)
    {
        for (sl = dblist; sl; sl = sl->next)
            create_database(sl->str);
        for (sl = extraroles; sl; sl = sl->next)
            create_role(sl->str, dblist);
    }

    /*
     * Ready to run the tests
     */
    header(_("running regression test queries"));

    for (sl = schedulelist; sl != NULL; sl = sl->next)
    {
        run_schedule(sl->str, tfunc);
    }

    for (sl = extra_tests; sl != NULL; sl = sl->next)
    {
        run_single_test(sl->str, tfunc);
    }

    /*
     * Shut down temp installation's postmaster
     */
    if (temp_instance)
    {
        header(_("shutting down postmaster"));
        stop_postmaster();
    }

    /*
     * If there were no errors, remove the temp instance immediately to
     * conserve disk space.  (If there were errors, we leave the instance in
     * place for possible manual investigation.)
     */
    if (temp_instance && fail_count == 0 && fail_ignore_count == 0)
    {
        header(_("removing temporary instance"));
        if (!rmtree(temp_instance, true))
            fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
                    progname, temp_instance);
    }

    fclose(logfile);

    /*
     * Emit nice-looking summary message
     */
    if (fail_count == 0 && fail_ignore_count == 0)
        snprintf(buf, sizeof(buf),
                 _(" All %d tests passed. "),
                 success_count);
    else if (fail_count == 0)    /* fail_count=0, fail_ignore_count>0 */
        snprintf(buf, sizeof(buf),
                 _(" %d of %d tests passed, %d failed test(s) ignored. "),
                 success_count,
                 success_count + fail_ignore_count,
                 fail_ignore_count);
    else if (fail_ignore_count == 0)    /* fail_count>0 && fail_ignore_count=0 */
        snprintf(buf, sizeof(buf),
                 _(" %d of %d tests failed. "),
                 fail_count,
                 success_count + fail_count);
    else
        /* fail_count>0 && fail_ignore_count>0 */
        snprintf(buf, sizeof(buf),
                 _(" %d of %d tests failed, %d of these failures ignored. "),
                 fail_count + fail_ignore_count,
                 success_count + fail_count + fail_ignore_count,
                 fail_ignore_count);

    putchar('\n');
    for (i = strlen(buf); i > 0; i--)
        putchar('=');
    printf("\n%s\n", buf);
    for (i = strlen(buf); i > 0; i--)
        putchar('=');
    putchar('\n');
    putchar('\n');

    if (file_size(difffilename) > 0)
    {
        printf(_("The differences that caused some tests to fail can be viewed in the\n"
                 "file \"%s\".  A copy of the test summary that you see\n"
                 "above is saved in the file \"%s\".\n\n"),
               difffilename, logfilename);
    }
    else
    {
        unlink(difffilename);
        unlink(logfilename);
    }

    if (fail_count != 0)
        exit(1);

    return 0;
}
